// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0.
// 
// Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0.
// 
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System;
using System.Collections.Generic;
using System.Text;

namespace TestUtilities.Ben.Demystifier {
    // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
    internal sealed class TypeNameHelper {
        private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string> {
            { typeof(void), "void" },
            { typeof(bool), "bool" },
            { typeof(byte), "byte" },
            { typeof(char), "char" },
            { typeof(decimal), "decimal" },
            { typeof(double), "double" },
            { typeof(float), "float" },
            { typeof(int), "int" },
            { typeof(long), "long" },
            { typeof(object), "object" },
            { typeof(sbyte), "sbyte" },
            { typeof(short), "short" },
            { typeof(string), "string" },
            { typeof(uint), "uint" },
            { typeof(ulong), "ulong" },
            { typeof(ushort), "ushort" }
        };

        /// <summary>
        /// Pretty print a type name.
        /// </summary>
        /// <param name="type">The <see cref="Type"/>.</param>
        /// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
        /// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
        /// <returns>The pretty printed type name.</returns>
        public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) {
            var builder = new StringBuilder();
            ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
            return builder.ToString();
        }

        private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) {
            if (type.IsGenericType) {
                var genericArguments = type.GetGenericArguments();
                ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
            } else if (type.IsArray) {
                ProcessArrayType(builder, type, options);
            } else if (_builtInTypeNames.TryGetValue(type, out var builtInName)) {
                builder.Append(builtInName);
            } else if (type.Namespace == nameof(System)) {
                builder.Append(type.Name);
            } else if (type.IsGenericParameter) {
                if (options.IncludeGenericParameterNames) {
                    builder.Append(type.Name);
                }
            }
            else {
                builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
            }
        }

        private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) {
            var innerType = type;
            while (innerType != null && innerType.IsArray){
                innerType = innerType.GetElementType();
            }

            ProcessType(builder, innerType, options);

            while (type != null && type.IsArray) {
                builder.Append('[');
                builder.Append(',', type.GetArrayRank() - 1);
                builder.Append(']');
                type = type.GetElementType();
            }
        }

        private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) {
            var offset = 0;
            if (type.IsNested && type.DeclaringType != null) {
                offset = type.DeclaringType.GetGenericArguments().Length;
            }

            if (options.FullName) {
                if (type.IsNested) {
                    ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
                    builder.Append('+');
                } else if (!string.IsNullOrEmpty(type.Namespace)) {
                    builder.Append(type.Namespace);
                    builder.Append('.');
                }
            }

            var genericPartIndex = type.Name.IndexOf('`');
            if (genericPartIndex <= 0) {
                builder.Append(type.Name);
                return;
            }

            builder.Append(type.Name, 0, genericPartIndex);

            builder.Append('<');
            for (var i = offset; i < length; i++) {
                ProcessType(builder, genericArguments[i], options);
                if (i + 1 == length) {
                    continue;
                }

                builder.Append(',');
                if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) {
                    builder.Append(' ');
                }
            }
            builder.Append('>');
        }

        private struct DisplayNameOptions {
            public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) {
                FullName = fullName;
                IncludeGenericParameterNames = includeGenericParameterNames;
            }

            public bool FullName { get; }
            public bool IncludeGenericParameterNames { get; }
        }
    }
}
